import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;

import javax.swing.JFrame;
import cern.colt.Timer;
import edu.uci.ics.jung.algorithms.layout.*;
import edu.uci.ics.jung.algorithms.metrics.Metrics;
import edu.uci.ics.jung.graph.*;
import edu.uci.ics.jung.visualization.BasicVisualizationServer;

public class main {
	public static double[] LifeTimeProb = new double[100];
	public static int NODE_NUM = 0;
	public static int NODE_COUNT = 0;
	public static int EDGE_NUM = 0;
	public static int EDGE_COUNT = 0;
	public static int INIT_NODE_AMOUNT = 500;
	public static int TERMINATE_COND = 10000;
	public static int NODE_AMOUNT = 20;
	public static int EDGE_AMOUNT = 30;
	public static int AGE_MAX = 80;
	public static int POPULATION = 0;
	//public static float POPULATION_GROWTH = (float) 1.0;
	public static double IMMIGRANT_RATE = (double)0.1;
	//public static float DEATH_RATE = (float) 0.03;
	//public static float BORN_RATE = (float) 0.01;
	//public static float DEATH_SLOPE = (float) 0.01;
	public static int AGE_INTERVAL = 10; 
	public static int TOTAL_AGE = 100;
	public static int[] deathToll = new int[TOTAL_AGE*2];
	public static double immiRate = IMMIGRANT_RATE;
	public static HashMap<Integer, ArrayList<MyNode>> nodelist = new HashMap<Integer, ArrayList<MyNode>>();
	public static UndirectedGraph x = new UndirectedSparseGraph();	
	public static Metrics MyMetrics = new Metrics(); 
	//public static AddEdge ADDEDGE = new AddEdge();
	
	public static void readLifeTime(){
		int LifeAge=0;
		try{
			// Open the file that is the first 
			// command line parameter
			FileInputStream fstream = new FileInputStream("LifeTimeDistribution");
			DataInputStream in = new DataInputStream(fstream);
	        BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine = null;			
			//Read File Line By Line
			while ((strLine = br.readLine()) != null) 	{
				// Print the content on the console
				// System.out.println (strLine);
				LifeTimeProb[LifeAge] = Double.parseDouble(strLine);
				LifeAge++;
			}
			in.close();
			}catch (Exception e){//Catch exception if any
				System.err.println("Error: " + e.getMessage());
		}
	}
	
	public static void writeAge(String fileName){
		try{
			
			File file =new File(fileName);
			if(!file.exists()){
	    		file.createNewFile();
	    	    }
	 
	    	    FileWriter fw = new FileWriter(file.getName());
	    	    BufferedWriter bw = new BufferedWriter(fw);
	    	    for(Integer age : nodelist.keySet()){
	    	       bw.write(age + " " + nodelist.get(age).size());
	    	       bw.newLine();
	    	    }
	    	    bw.close();
			}catch (Exception e){//Catch exception if any
				System.err.println("Error: " + e.getMessage());
		}
	}
	
	public static void writeLifeTime(String fileName){
		try{	
			File file =new File(fileName);
			if(!file.exists()){
	    		file.createNewFile();
	    	    }
	 
	    	    FileWriter fw = new FileWriter(file.getName());
	    	    BufferedWriter bw = new BufferedWriter(fw);
	    	    for(Integer age : nodelist.keySet()){
	    	       bw.write(age + " " + deathToll[age]);
	    	       bw.newLine();
	    	    }
	    	    bw.close();
			}catch (Exception e){//Catch exception if any
				System.err.println("Error: " + e.getMessage());
		}
	}
	
	public static float bornRate(int nowYear){
		float dynamic_bornRate = (float) 0.045;
		
		if(nowYear <= 100){
			dynamic_bornRate = (float) 0.045;
		}
		else if(nowYear <= 500){
			dynamic_bornRate = (float) ((float) 0.0035 + Math.pow(nowYear-65, -0.9));
		}
		else{
			dynamic_bornRate = (float) ((float) 0.0035 + Math.pow(500-65, -0.9));
		}
		return dynamic_bornRate;
	}
	
	public static float deathRate(int nowYear){
		float dynamic_deathRate;
		if(nowYear <= 500){
			dynamic_deathRate = (float) Math.pow(nowYear+70, -0.8);
		}
		else{
			dynamic_deathRate = (float) Math.pow(500+70, -0.8);
		}		
		return dynamic_deathRate;
	}
	
	public static float deathOfAge(int age){
		float deathAge = 0;
		if(age < 12){
			deathAge = (float) Math.pow(age+3, -2);
		}
		else{
			deathAge = (float) ((float) (Math.exp(((double)age+6)*0.115)-1)/200000 );
		}
		return deathAge;
		
	}
	
	public static float ratioOfRoughDR(int nowYear){
		float deathNum_rough =  (deathRate(nowYear) * POPULATION);
		
//		for(Integer age : nodelist.keySet()){
//			if(age >= 105){
//				deathNum_rough += nodelist.get(age).size();
//			}
//		}
		
		float deathNum_ageDist = 0;
		
		for(Integer age : nodelist.keySet()){
			//System.out.println("in ratio, age = " + age + ", num = " + nodelist.get(age).size());

			//if(age < 105)
				deathNum_ageDist += deathOfAge(age)*nodelist.get(age).size();
			//else
				//deathNum_ageDist += nodelist.get(age).size();

		}
		return (float) ((deathNum_rough / deathNum_ageDist)*0.9);
	}
	
	public static HashMap<Integer, ArrayList<MyNode>> AnnualUpdate(HashMap<Integer, ArrayList<MyNode>> nodelist, int nowYear){
//		for(int i=0; i<nodelist.size(); i++){
//			nodelist.get(i).age++;
//		}
		//for each node set, add age 1
		ArrayList<MyNode> temp;
		HashMap<Integer, ArrayList<MyNode>> temp_nodelist = new HashMap<Integer, ArrayList<MyNode>>();
		for(Integer age : nodelist.keySet()){
			temp = nodelist.get(age);
			temp_nodelist.put(age+1, temp);
			for(int i = 0; i < temp.size(); i++){
				temp.get(i).age++;
			}
			//System.out.println("annual right age = " + age + ", num = " + temp_nodelist.get(age+1).size());
		}
		nodelist = temp_nodelist;
		
//		for(int i : nodelist.keySet()){
//			System.out.println("annual age = " + i + ", num = " + nodelist.get(i).size());
//		}
		
		int max_age = 0;
		Iterator it = nodelist.keySet().iterator();
		while(it.hasNext()){
			max_age = (Integer) it.next();
		}
		
		for(Integer i : nodelist.keySet()){			
			deathToll[i] = (int) (ratioOfRoughDR(nowYear) * deathOfAge(i) * nodelist.get(i).size());
		}
		
		immiRate = (double)((TERMINATE_COND-POPULATION) / TERMINATE_COND); 
		// TODO add link to the graph
		
		return nodelist;
		
	}
	
	/* --------------- method:initFunction ---------------
	 * initialization: to make original resident generated 
	 * return: void										
	 * ---------------------------------------------------*/
	public static void initFunction(HashMap<Integer, ArrayList<MyNode>> nlist, UndirectedGraph x){
		int age;
		Random r = new Random();
		MyNode tempNode=null, linkNode;
		int pickNodeNum;
		int rndAge = (int) ((Math.random()*70));
		float rnd = (float)Math.random();
		/* --------------- node addition ---------------*/
		for(int i=0;i<INIT_NODE_AMOUNT;i++){
			age = -1;			
			while(age < 0 || age > 70){
			    age = (int) (r.nextGaussian() * 15 + 35);
			}	
			x.addVertex(tempNode =  new MyNode(NODE_NUM++, age ) );
			POPULATION++;
//			nodelist.add( NodeTemp );
			
			if(nodelist.containsKey(age)){
				nodelist.get(age).add(tempNode);
			}
			else{
				ArrayList<MyNode> temp = new ArrayList<MyNode>();
				temp.add(tempNode);
				nodelist.put(age, temp);
			}			
		}
		/*--------------- edge addition ---------------*/
		while(!nodelist.containsKey(rndAge)){
			rndAge = (int) ((Math.random()*70));
		}
		pickNodeNum = (int) ((nodelist.get(rndAge).size()-1)*rnd);
		x.addEdge( (EDGE_NUM++)+"", tempNode, linkNode = (nodelist.get(rndAge).get( (int) (pickNodeNum) )) );		
	}
	/* --------------- method:movinFunction ---------------
	 * make immigrant people in 
	 * return: total immigrate amount (int)		
	 * ----------------------------------------------------*/
	public static int moveinFunction(HashMap<Integer, ArrayList<MyNode>> nlist, UndirectedGraph x, int nowYear){
		int age;
		Random r = new Random();
		MyNode NodeTemp;
		for(int i=0;i<POPULATION*immiRate;i++){
			age = -1;
			while(age < 0 || age > 70){
			    age = (int) r.nextGaussian() * 15 + 35;
			}
			x.addVertex(NodeTemp =  new MyNode(NODE_NUM++, age) );
			POPULATION++;
			//nodelist.add(NodeTemp);
			if(nodelist.containsKey(age)){
				nodelist.get(age).add(NodeTemp);
			}
			else{
				ArrayList<MyNode> temp = new ArrayList<MyNode>();
				temp.add(NodeTemp);
				nodelist.put(age, temp);
			}
			//TODO update IMMIGRANT_RATE
		}
		return (int)(POPULATION*immiRate);
	}
	/* --------------- method:bornFunction ---------------
	 * give the infant age 0 born by the born toll 
	 * which we just calculated. 
	 * return: total born infant amount (int)		
	 * ---------------------------------------------------*/
	public static int bornFunction(HashMap<Integer, ArrayList<MyNode>> nlist, UndirectedGraph x, int nowYear){
		int bornTotal = (int)(bornRate(nowYear)*POPULATION);
		MyNode bornNode = null;
		EDGE_COUNT = 0;
			
		for(int i=0; i < bornTotal ;i++ ){
			/*--------- node addition---------*/
			x.addVertex(bornNode = new MyNode(NODE_NUM++, 0) );
			if(nodelist.containsKey(0)){
				nodelist.get(0).add(bornNode);
			}else{
				ArrayList<MyNode> temp = new ArrayList<MyNode>();
				temp.add(bornNode);
				nodelist.put(0, temp);
			}
			POPULATION++;
			NODE_COUNT++;
			/* ---------edge addition ---------*/			
			MyNode parentNode = null;
			int rndAge = (int) ((Math.random()*19)+20);
			float rnd = (float)Math.random();
			int pickNodeNum = 0; /* record the people amount in specified age */
			
			for(int j=0;j<2;j++){ 
				rnd = (int) Math.random();
				rndAge = (int) ((Math.random()*19)+20);
				while(!nodelist.containsKey(rndAge)){
					System.out.print("選的年齡"+rndAge +"沒人啦" );
					rndAge = (int) ((Math.random()*19)+20);
					System.out.println(" 重新選" + rndAge+"歲的人");
				}

				/* pick a random index node from specified age */
				pickNodeNum = (int) ((nodelist.get(rndAge).size()-1)*rnd);
				/*  add edge different from before*/
				while( parentNode == nodelist.get(rndAge).get(pickNodeNum) ){
					rnd = (int) Math.random(); 
					rndAge = (int) ((Math.random()*19)+20);
					while(!nodelist.containsKey(rndAge))
						rndAge = (int) ((Math.random()*19)+20);
					pickNodeNum = (int) ( (nodelist.get(rndAge).size()-1)*rnd);
				}
				
				x.addEdge( (EDGE_NUM++)+"", bornNode, parentNode = (nodelist.get(rndAge).get( (int) (pickNodeNum) )) );
				bornNode.setParent(parentNode);
				/* produce or find a exist couple to be parents*/
				if(j==0){
					if(parentNode.spouse!=null){
						x.addEdge( (EDGE_NUM++)+"", bornNode ,parentNode.spouse );						
						j = 10;
						EDGE_COUNT++;
					}
				}else if(j==1){
					x.addEdge( (EDGE_NUM++)+"", bornNode.parent[0] ,bornNode.parent[1] );
					bornNode.parent[0].setSpouse(bornNode.parent[1]);
					bornNode.parent[1].setSpouse(bornNode.parent[0]);
					EDGE_COUNT += 2;
				}
			}
		}
		return bornTotal;
	}
	/* --------------- method:deathFunction ---------------
	 * kill the person by the death toll we just calculated 
	 * return: total death peoeple amount (int)		*/
	public static int deathFunction(HashMap<Integer, ArrayList<MyNode>> nlist, UndirectedGraph x ){
		MyNode killingNode;
		ArrayList<Integer> nodelist_empty_index = new ArrayList<Integer>();
		int deathTotal = 0;
		for(Integer age : nodelist.keySet()){
			//System.out.println("age = " + age);			

			//if(nodelist.get(age).size() > deathToll[age] && age < 105){
			if(nodelist.get(age).size() > deathToll[age]){
				//System.out.println("death age = " + age + ", num = " + deathToll[age]);
				for(int i = 0; i < deathToll[age]; ++i){
					killingNode = nodelist.get(age).remove(0);
					x.removeVertex(killingNode);
				}
				deathTotal += deathToll[age];
				POPULATION = POPULATION - deathToll[age];
			}
			else{
				//System.out.println("death age = " + age + ", num = " + nodelist.get(age).size());
				POPULATION = POPULATION - nodelist.get(age).size();
				nodelist_empty_index.add(age);
				for(int i = 0; i < nodelist.get(age).size(); ++i){
					killingNode = nodelist.get(age).remove(0);
					x.removeVertex(killingNode);
				}
				deathTotal += nodelist.get(age).size();
			}
			
		}
		//remove those empty entry in nodelist
		for(Integer i : nodelist_empty_index){
			nodelist.remove(i);
		}
		return deathTotal;
	}
	
	
	public static int addEdgeFunction(HashMap<Integer, ArrayList<MyNode>> nlist, UndirectedGraph x ){		
		for(int age : nodelist.keySet()){
			for(MyNode node:nodelist.get(age)){
				
			}
		}
		return 0;
	}

	public static void main(String[] args) {
		int source, target;
		int nowYear=1;		
		int age;
		int annualdeath, annualborn, annualmovein;
	
		MyNode NodeTemp;
		int TotalAge=0;
		Random r = new Random();
		boolean flag_write = true;
		
		readLifeTime();
		initFunction(nodelist, x);
		
		/* start life */
		while(POPULATION < TERMINATE_COND ){	
			
			annualmovein = moveinFunction(nodelist,x, nowYear);
			annualborn = bornFunction(nodelist,x, nowYear);
			annualdeath = deathFunction(nodelist, x);
			nodelist = AnnualUpdate(nodelist,nowYear);
			
			System.out.println("IRLAB第" + (nowYear++) +"年");
			System.out.println("進來了 " + annualmovein);
			System.out.println("出生了 " + annualborn);
			System.out.println("新關係 " + EDGE_COUNT);
			System.out.println("死掉了 " + annualdeath);
			System.out.println("粗死亡率殺的 " + deathRate(nowYear) * POPULATION);				
			System.out.println("POPULATION: " + POPULATION);
			System.out.println("");
			
			if(POPULATION > TERMINATE_COND * 0.9 && flag_write){
				writeLifeTime("LifeTimeDistribution");
				flag_write = false;
			}
			
		}
		//System.out.println("the Graph g=" + x.toString() + "edge:" + x.getEdgeCount() );
		for(Integer i : nodelist.keySet()){
			TotalAge += nodelist.get(i).size()*i;
		}
		System.out.println("\n\nAverage Age : "+ (double)TotalAge / POPULATION);
		for(Integer i : nodelist.keySet()){
		    System.out.println("age = " + i + ", num = " + nodelist.get(i).size());
		} 	
		
		System.out.println("現在的圖"+ EDGE_NUM + ", 總共出生" + NODE_COUNT);
		
		System.out.println("POPULATION = " + POPULATION);
		//System.out.println("Clustering Coefficients = " +  Metrics.clusteringCoefficients(x).values() );
		writeAge("SocietyAgeDistribution");
		double cc_sum = 0;
		
		
		Iterator it = Metrics.clusteringCoefficients(x).values().iterator();
		int CCsize = Metrics.clusteringCoefficients(x).values().size();
		
		for(int i=0;i<CCsize;i++){
			//if(it.hasNext())
				cc_sum += Double.parseDouble( it.next().toString() );		
		}
		System.out.println("Clustering Coefficients = " +  cc_sum/CCsize);
		
		/* !!!!!!!!!!!!!!!!!!!!! U I !!!!!!!!!!!!!!!!!
		Layout<Integer, String> layout = new CircleLayout(x);
		layout.setSize(new Dimension(400,400)); // sets the initial size of the space
	     // The BasicVisualizationServer<V,E> is parameterized by the edge types
	    BasicVisualizationServer<Integer,String> vv = 
	              new BasicVisualizationServer<Integer,String>(layout);
	    vv.setPreferredSize(new Dimension(500,500)); //Sets the viewing area size
	    
	    JFrame frame = new JFrame("Simple Graph View");
	    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    frame.getContentPane().add(vv); 
	    frame.pack();
	    frame.setVisible(true);
		*/	
	}	
}

